//
//  UITableView+YHExtension.swift
//  Adorbee
//
//  Created by amovision on 2025/5/12.
//

import ObjectiveC.runtime
import UIKit

public extension UITableView {
  var numOfSections: (() -> (Int))? {
    set { objc_setAssociatedObject(self, #function.pointer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    get { objc_getAssociatedObject(self, #function.pointer) as? () -> (Int) }
  }
    
  var numOfRows: ((Int) -> (Int))? {
    set { objc_setAssociatedObject(self, #function.pointer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    get { objc_getAssociatedObject(self, #function.pointer) as? (Int) -> (Int) }
  }
    
  var cellForRows: ((Int, Int) -> UITableViewCell)? {
    set { objc_setAssociatedObject(self, #function.pointer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    get { objc_getAssociatedObject(self, #function.pointer) as? (Int, Int) -> UITableViewCell }
  }
    
  var didSelectRow: ((Int, Int) -> Void)? {
    set { objc_setAssociatedObject(self, #function.pointer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    get { objc_getAssociatedObject(self, #function.pointer) as? (Int, Int) -> Void }
  }
    
  var heightForRows: ((Int, Int) -> (CGFloat))? {
    set { objc_setAssociatedObject(self, #function.pointer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    get { objc_getAssociatedObject(self, #function.pointer) as? (Int, Int) -> (CGFloat) }
  }
    
  var heightForHeader: CGFloat {
    set { objc_setAssociatedObject(self, #function.pointer, newValue, .OBJC_ASSOCIATION_ASSIGN) }
    get { objc_getAssociatedObject(self, #function.pointer) as? CGFloat ?? 0.1 }
  }
    
  var heightForHeaders: ((Int) -> (CGFloat))? {
    set { objc_setAssociatedObject(self, #function.pointer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    get { objc_getAssociatedObject(self, #function.pointer) as? (Int) -> (CGFloat) }
  }
    
  var heightForFooter: CGFloat {
    set { objc_setAssociatedObject(self, #function.pointer, newValue, .OBJC_ASSOCIATION_ASSIGN) }
    get { objc_getAssociatedObject(self, #function.pointer) as? CGFloat ?? 0.1 }
  }
    
  var heightForFooters: ((Int) -> (CGFloat))? {
    set { objc_setAssociatedObject(self, #function.pointer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    get { objc_getAssociatedObject(self, #function.pointer) as? (Int) -> (CGFloat) }
  }
    
  var leadingSwipeAction: ((Int, Int) -> [UIContextualAction])? {
    set { objc_setAssociatedObject(self, #function.pointer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    get { objc_getAssociatedObject(self, #function.pointer) as? (Int, Int) -> [UIContextualAction] }
  }
    
  var trailingSwipeAction: ((Int, Int) -> [UIContextualAction])? {
    set { objc_setAssociatedObject(self, #function.pointer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    get { objc_getAssociatedObject(self, #function.pointer) as? (Int, Int) -> [UIContextualAction] }
  }
    
  var canEditRows: ((Int, Int) -> Bool)? {
    set { objc_setAssociatedObject(self, #function.pointer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    get { objc_getAssociatedObject(self, #function.pointer) as? (Int, Int) -> Bool }
  }
    
  var canMoveRows: ((Int, Int) -> Bool)? {
    set { objc_setAssociatedObject(self, #function.pointer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    get { objc_getAssociatedObject(self, #function.pointer) as? (Int, Int) -> Bool }
  }
    
  var canRemoveRows: ((Int, Int) -> Bool)? {
    set { objc_setAssociatedObject(self, #function.pointer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    get { objc_getAssociatedObject(self, #function.pointer) as? (Int, Int) -> Bool }
  }
    
  var moveRows: ((Int, Int, Int, Int) -> Void)? {
    set { objc_setAssociatedObject(self, #function.pointer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    get { objc_getAssociatedObject(self, #function.pointer) as? (Int, Int, Int, Int) -> Void }
  }
    
  var commitEditingStyle: ((Int, Int) -> UITableViewCell.EditingStyle)? {
    set { objc_setAssociatedObject(self, #function.pointer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    get { objc_getAssociatedObject(self, #function.pointer) as? (Int, Int) -> UITableViewCell.EditingStyle }
  }
    
  var viewForHeaders: ((Int) -> (UIView?))? {
    set { objc_setAssociatedObject(self, #function.pointer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    get { objc_getAssociatedObject(self, #function.pointer) as? (Int) -> (UIView?) }
  }
    
  var viewForFooters: ((Int) -> (UIView?))? {
    set { objc_setAssociatedObject(self, #function.pointer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    get { objc_getAssociatedObject(self, #function.pointer) as? (Int) -> (UIView?) }
  }
    
  var shouldIntentWhileEditing: ((Int, Int) -> Bool)? {
    set { objc_setAssociatedObject(self, #function.pointer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    get { objc_getAssociatedObject(self, #function.pointer) as? (Int, Int) -> Bool }
  }
    
  var proposedMoved: ((Int, Int, Int, Int) -> (Int, Int))? {
    set { objc_setAssociatedObject(self, #function.pointer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    get { objc_getAssociatedObject(self, #function.pointer) as? (Int, Int, Int, Int) -> (Int, Int) }
  }
        
  var registerCells: [UITableViewCell.Type]? {
    set { objc_setAssociatedObject(self, #function.pointer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    get { objc_getAssociatedObject(self, #function.pointer) as? [UITableViewCell.Type] }
  }
  
  func setAutoRowHeight(_ height: CGFloat) {
    rowHeight = UITableView.automaticDimension
    estimatedRowHeight = height
  }
    
  convenience init(cells: UITableViewCell.Type..., style: UITableView.Style = .plain, separator: UITableViewCell.SeparatorStyle = .none) {
    self.init(frame: .zero, style: style)
    separatorStyle = separator
    showsVerticalScrollIndicator = false
    backgroundColor = .clear
    registerCells = cells.map { $0 }
    for cell in cells {
      register(cell, forCellReuseIdentifier: NSStringFromClass(cell))
    }
    delegate = self
    dataSource = self
  }
}

extension UITableView: @retroactive UIScrollViewDelegate {}
extension UITableView: @retroactive UITableViewDataSource {
  public func numberOfSections(in tableView: UITableView) -> Int {
    numOfSections?() ?? 1
  }
    
  public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    numOfRows?(section) ?? 0
  }
    
  public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    cellForRows?(indexPath.section, indexPath.row) ?? UITableViewCell()
  }
    
  public func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
    canEditRows?(indexPath.section, indexPath.row) ?? false
  }
    
  public func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
    canMoveRows?(indexPath.section, indexPath.row) ?? false
  }
    
  public func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
    commitEditingStyle?(indexPath.section, indexPath.row) ?? .none
  }
}

extension UITableView: @retroactive UITableViewDelegate {
  public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    didSelectRow?(indexPath.section, indexPath.row)
  }
    
  public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    heightForRows?(indexPath.section, indexPath.row) ?? tableView.rowHeight
  }
    
  public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    heightForHeaders?(section) ?? heightForHeader
  }
    
  public func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
    heightForFooters?(section) ?? heightForFooter
  }
    
  public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    viewForHeaders?(section) ?? UIView(frame: CGRectMake(0, 0, UIScreen.main.bounds.width, heightForHeader))
  }
    
  public func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
    viewForFooters?(section) ?? UIView(frame: CGRectMake(0, 0, UIScreen.main.bounds.width, heightForFooter))
  }
    
  public func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
    if let actions = leadingSwipeAction?(indexPath.section, indexPath.row) {
      .init(actions: actions)
    } else { nil }
  }
    
  public func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
    if let actions = trailingSwipeAction?(indexPath.section, indexPath.row) {
      .init(actions: actions)
    } else { nil }
  }
    
  public func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
    moveRows?(sourceIndexPath.section, sourceIndexPath.row, destinationIndexPath.section, destinationIndexPath.row)
  }
    
  public func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
    shouldIntentWhileEditing?(indexPath.section, indexPath.row) ?? false
  }
    
  public func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
    if let proposedMoved {
      let (s, r) = proposedMoved(sourceIndexPath.section, sourceIndexPath.row, proposedDestinationIndexPath.section, proposedDestinationIndexPath.row)
      return IndexPath(row: r, section: s)
    } else {
      return proposedDestinationIndexPath
    }
  }
}

public extension UITableView {
  func register<T: UITableViewCell>(cells: T.Type ...) {
    for cell in cells {
      register(cell, forCellReuseIdentifier: NSStringFromClass(cell))
    }
  }
    
  func register<T: UITableViewHeaderFooterView>(headers: T.Type ...) {
    for header in headers {
      register(header, forHeaderFooterViewReuseIdentifier: NSStringFromClass(header))
    }
  }
    
  func dequeueReusableCell<T: UITableViewCell>(named name: T.Type, configCell: ((T) -> Void)? = nil) -> T {
    guard let cell = dequeueReusableCell(withIdentifier: NSStringFromClass(name)) as? T else {
      fatalError(
        "Couldn't find UITableViewCell for \(NSStringFromClass(name)), make sure the cell is registered with table view")
    }
    configCell?(cell)
    return cell
  }

  func dequeueReusableCell<T: UITableViewCell>(named name: T.Type, for indexPath: IndexPath, configCell: ((T) -> Void)? = nil) -> T {
    guard let cell = dequeueReusableCell(withIdentifier: NSStringFromClass(name), for: indexPath) as? T else {
      fatalError(
        "Couldn't find UITableViewCell for \(NSStringFromClass(name)), make sure the cell is registered with table view")
    }
    configCell?(cell)
    return cell
  }
    
  func dequeueReusableHeaderFooter<T: UITableViewHeaderFooterView>(named name: T.Type, for indexPath: IndexPath) -> T {
    guard let header = dequeueReusableHeaderFooterView(withIdentifier: NSStringFromClass(name)) as? T else {
      fatalError(
        "Couldn't find UITableViewHeaderFooterView for \(NSStringFromClass(name)), make sure the header or footer is registered with table view")
    }
    return header
  }
}
